/*
 * Copyright (C) 2009 Jakub Miara
 * Copyright (C) 2009 The Android Open Source Project (DaysOfWeek class)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.araim.rringtone;

import java.text.DateFormatSymbols;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.media.RingtoneManager;
import android.net.Uri;
import android.util.Log;

import com.araim.rringtone.db.RandomRingtoneDatabase;

public class Alarm {


	public static final Uri CONTENT_URI = Uri.parse("content://com.android.alarmclock/alarm");
	public static final Uri NEXUS_CONTENT_URI = Uri.parse("content://com.android.deskclock/alarm");

	public static final String ID = "_ID";
	public static final String HOUR = "hour";
	public static final String MINUTES = "minutes";
	public static final String DAYS_OF_WEEK = "daysofweek";
	public static final String ALARM_TIME = "alarmtime";
	public static final String ENABLED = "enabled";
	public static final String VIBRATE = "vibrate";
	public static final String MESSAGE = "message";
	public static final String ALERT = "alert";
	public static final String DEFAULT_SORT_ORDER = ID + " ASC";
	public static final String WHERE_ENABLED = ENABLED + "=1";
	public static final String ALARM_ALERT_SILENT = "silent";

	public static final String PM = "pm";

	static final String[] ALARM_QUERY_COLUMNS = { ID, HOUR, MINUTES, DAYS_OF_WEEK, ALARM_TIME, ENABLED, VIBRATE, MESSAGE, ALERT };

	public static final int COLUMN_ID_INDEX = 0;
	public static final int COLUMN_HOUR_INDEX = 1;
	public static final int COLUMN_MINUTES_INDEX = 2;
	public static final int COLUMN_DAYS_OF_WEEK_INDEX = 3;
	public static final int COLUMN_TIME_INDEX = 4;
	public static final int COLUMN_ENABLED_INDEX = 5;
	public static final int COLUMN_VIBRATE_INDEX = 6;
	public static final int COLUMN_MESSAGE_INDEX = 7;
	public static final int COLUMN_ALERT_INDEX = 8;

	private int id;
	private boolean enabled;
	private int hour;
	private int minutes;
	private DaysOfWeek daysOfWeek;
	private long time;
	private boolean vibrate;
	private String label;
	private Uri alert;

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public boolean isEnabled() {
		return enabled;
	}

	public void setEnabled(boolean enabled) {
		this.enabled = enabled;
	}

	public int getHour() {
		return hour;
	}

	public void setHour(int hour) {
		this.hour = hour;
	}

	public int getMinutes() {
		return minutes;
	}

	public void setMinutes(int minutes) {
		this.minutes = minutes;
	}

	public DaysOfWeek getDaysOfWeek() {
		return daysOfWeek;
	}

	public void setDaysOfWeek(DaysOfWeek daysOfWeek) {
		this.daysOfWeek = daysOfWeek;
	}

	public long getTime() {
		return time;
	}

	public void setTime(long time) {
		this.time = time;
	}

	public boolean isVibrate() {
		return vibrate;
	}

	public void setVibrate(boolean vibrate) {
		this.vibrate = vibrate;
	}

	public String getLabel() {
		return label;
	}

	public void setLabel(String label) {
		this.label = label;
	}

	public Uri getAlert() {
		return alert;
	}

	public void setAlert(Uri alert) {
		this.alert = alert;
	}

	public boolean isSilent() {
		return ALARM_ALERT_SILENT.equals(alert);
	}

	public static Alarm[] getAllAlarms(Context ctx) {
		return getAllAlarmsList(ctx).toArray(new Alarm[0]);
	}

	public static ArrayList<Map<String, String>> getAllAlarmsListMap(Context ctx) {
		Cursor c = ctx.getContentResolver().query(CONTENT_URI, ALARM_QUERY_COLUMNS, null, null, DEFAULT_SORT_ORDER);
		if (c == null) {
			c = ctx.getContentResolver().query(NEXUS_CONTENT_URI, ALARM_QUERY_COLUMNS, null, null, DEFAULT_SORT_ORDER);
		}
		ArrayList<Map<String, String>> al = new ArrayList<Map<String, String>>();
		NumberFormat formatter = new DecimalFormat("00");
		while (c.moveToNext()) {
			Map<String, String> m = new HashMap<String, String>();
			int hrs = c.getInt(COLUMN_HOUR_INDEX);
			if (hrs > 12) {
				m.put(PM, "true");
			}
			m.put(ALERT, c.getString(COLUMN_ALERT_INDEX));
			m.put(DAYS_OF_WEEK, new DaysOfWeek(c.getInt(COLUMN_DAYS_OF_WEEK_INDEX)).toString());
			m.put(ALARM_TIME, formatter.format(hrs) + ":" + formatter.format(c.getInt(COLUMN_MINUTES_INDEX)));
			m.put(MESSAGE, c.getString(COLUMN_MESSAGE_INDEX));
			m.put(ID, c.getString(COLUMN_ID_INDEX));
			al.add(m);
		}
		return al;
	}

	public static ArrayList<Integer> getAllAlarmIds(Context ctx) {
		Cursor c = ctx.getContentResolver().query(CONTENT_URI, new String[] { ID }, null, null, DEFAULT_SORT_ORDER);
		if (c == null) {
			c = ctx.getContentResolver().query(NEXUS_CONTENT_URI, new String[] { ID }, null, null, DEFAULT_SORT_ORDER);
		}
		ArrayList<Integer> al = new ArrayList<Integer>();
		while (c.moveToNext()) {
			al.add(c.getInt(0));
		}
		return al;
	}

	public static ArrayList<Alarm> getAllAlarmsList(Context ctx) {
		Cursor c = ctx.getContentResolver().query(CONTENT_URI, ALARM_QUERY_COLUMNS, null, null, DEFAULT_SORT_ORDER);
		if (c == null) {
			c = ctx.getContentResolver().query(NEXUS_CONTENT_URI, ALARM_QUERY_COLUMNS, null, null, DEFAULT_SORT_ORDER);
		}
		ArrayList<Alarm> al = new ArrayList<Alarm>();
		while (c.moveToNext()) {
			Alarm a = new Alarm();
			a.setAlert(Uri.parse(c.getString(COLUMN_ALERT_INDEX)));
			a.setDaysOfWeek(new DaysOfWeek(c.getInt(COLUMN_DAYS_OF_WEEK_INDEX)));
			a.setEnabled(c.getInt(COLUMN_ENABLED_INDEX) == 1);
			a.setHour(c.getInt(COLUMN_HOUR_INDEX));
			a.setId(c.getInt(COLUMN_ID_INDEX));
			a.setLabel(c.getString(COLUMN_MESSAGE_INDEX));
			a.setMinutes(c.getInt(COLUMN_MINUTES_INDEX));
			a.setTime(c.getInt(COLUMN_TIME_INDEX));
			a.setVibrate(c.getInt(COLUMN_VIBRATE_INDEX) == 1);
			al.add(a);
		}
		return al;
	}

	public static Alarm getAlarm(Context ctx, int id) {
		Cursor c = ctx.getContentResolver().query(CONTENT_URI, ALARM_QUERY_COLUMNS, "_ID = ?", new String[] { String.valueOf(id) }, null);
		if (c == null) {
			c = ctx.getContentResolver()
					.query(NEXUS_CONTENT_URI, ALARM_QUERY_COLUMNS, "_ID = ?", new String[] { String.valueOf(id) }, null);
		}
		Alarm a = new Alarm();
		if (c.moveToNext()) {
			a.setAlert(Uri.parse(c.getString(COLUMN_ALERT_INDEX)));
			a.setDaysOfWeek(new DaysOfWeek(c.getInt(COLUMN_DAYS_OF_WEEK_INDEX)));
			a.setEnabled(c.getInt(COLUMN_ENABLED_INDEX) == 1);
			a.setHour(c.getInt(COLUMN_HOUR_INDEX));
			a.setId(c.getInt(COLUMN_ID_INDEX));
			a.setLabel(c.getString(COLUMN_MESSAGE_INDEX));
			a.setMinutes(c.getInt(COLUMN_MINUTES_INDEX));
			a.setTime(c.getInt(COLUMN_TIME_INDEX));
			a.setVibrate(c.getInt(COLUMN_VIBRATE_INDEX) == 1);

		} else {
			return null;
		}
		return a;
	}

	public static void setAlarmSound(Context context, int alarmid, String soundURI) {
		ContentValues cv = new ContentValues();
		cv.put("alert", soundURI);
		Uri u2 = Uri.parse("content://com.android.alarmclock/alarm/" + alarmid);
		try {
			context.getContentResolver().update(u2, cv, "ID = ?", new String[] { String.valueOf(alarmid) });
		} catch (Exception e) {

		}
		u2 = Uri.parse("content://com.android.deskclock/alarm/" + alarmid);
		try {
			context.getContentResolver().update(u2, cv, "ID = ?", new String[] { String.valueOf(alarmid) });
		} catch (Exception e) {

		}
	}

	public static void randomizeAlarm(Context context, int idd) {
		Random rr = new Random();
		boolean defaultAlarm = (idd == -1);
		ArrayList<Integer> ids;
		RandomRingtoneDatabase rdb = new RandomRingtoneDatabase(context);
		if (idd == -1) {
			ids = Alarm.getAllAlarmIds(context);
		} else {
			ids = new ArrayList<Integer>();
			ids.add(idd);
		}
		ArrayList<RingtoneInfo> rings = null;
		if (defaultAlarm) {
			rings = rdb.getAllRingtones(RingtoneManager.TYPE_ALARM, -1);
			Log.d(RandomRingtone.TAG, "Found " + rings.size() + " user selected alarms for ID: -1");
		}

		for (int id : ids) {
			if (!defaultAlarm) {
				rings = rdb.getAllRingtones(RingtoneManager.TYPE_ALARM, id);
				Log.d(RandomRingtone.TAG, "Found " + rings.size() + " user selected alarms for ID: " + id);
			}

			try {
				// do nothing if no ringtone has been selected
				if (rings.size() == 0) {
					return;
				}
				int num = -1;
				do {
					// get a random index
					num = rr.nextInt(rings.size());

					RingtoneInfo ri = rings.get(num);
					if (RingtoneInfo.isValidAlarm(context, ri)) {
						Alarm.setAlarmSound(context, id, ri.getFullUri());
						Log.i(RandomRingtone.TAG, "Alarm \"" + ri.getName() + "\" has been set");
						break;
					} else {
						Log.i(RandomRingtone.TAG, "Alarm \"" + ri.getName() + "\" is no longer valid");
						// the ringtone is no longer valid - remove it
						rings.remove(num);
						rdb.delete(ri);
						// if there are no more - don't set
						if (rings.size() == 0) {
							Log.i(RandomRingtone.TAG, "After removing invalid alarms there is not one left");
							return;
						}
					}
				} while (true);
			} catch (Exception e) {
				Log.e(RandomRingtone.TAG, "Error setting alarm: " + e.toString());
			}
		}
	}

	@Override
	public String toString() {
		return "Alarm [alert=" + alert + ", daysOfWeek=" + daysOfWeek + ", enabled=" + enabled + ", hour=" + hour + ", id=" + id
				+ ", label=" + label + ", minutes=" + minutes + ", time=" + time + ", vibrate=" + vibrate + "]";
	}

	/*
	 * Days of week code as a single int. 0x00: no day 0x01: Monday 0x02:
	 * Tuesday 0x04: Wednesday 0x08: Thursday 0x10: Friday 0x20: Saturday 0x40:
	 * Sunday
	 */
	static final class DaysOfWeek {

		private static int[] DAY_MAP = new int[] { Calendar.MONDAY, Calendar.TUESDAY, Calendar.WEDNESDAY, Calendar.THURSDAY,
				Calendar.FRIDAY, Calendar.SATURDAY, Calendar.SUNDAY, };

		// Bitmask of all repeating days
		private int mDays;

		DaysOfWeek(int days) {
			mDays = days;
		}

		public String toString() {
			StringBuilder ret = new StringBuilder();

			// count selected days
			int dayCount = 0, days = mDays;
			while (days > 0) {
				if ((days & 1) == 1)
					dayCount++;
				days >>= 1;
			}

			// short or long form?
			DateFormatSymbols dfs = new DateFormatSymbols();
			String[] dayList = (dayCount > 1) ? dfs.getShortWeekdays() : dfs.getWeekdays();

			// selected days
			for (int i = 0; i < 7; i++) {
				if ((mDays & (1 << i)) != 0) {
					ret.append(dayList[DAY_MAP[i]]);
					dayCount -= 1;
					if (dayCount > 0)
						ret.append(",");
				}
			}
			return ret.toString();
		}

		private boolean isSet(int day) {
			return ((mDays & (1 << day)) > 0);
		}

		public void set(int day, boolean set) {
			if (set) {
				mDays |= (1 << day);
			} else {
				mDays &= ~(1 << day);
			}
		}

		public void set(DaysOfWeek dow) {
			mDays = dow.mDays;
		}

		public int getCoded() {
			return mDays;
		}

		// Returns days of week encoded in an array of booleans.
		public boolean[] getBooleanArray() {
			boolean[] ret = new boolean[7];
			for (int i = 0; i < 7; i++) {
				ret[i] = isSet(i);
			}
			return ret;
		}

		public boolean isRepeatSet() {
			return mDays != 0;
		}
	}
}
